﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using aboveXWords.Properties;

namespace aboveXWords.GUI
{
  [Serializable]
  internal class XWord : List<Word>
  {
    private readonly Squares m_squares = new Squares();

    private Canvas m_canvas;

    public XWord(int width, int height)
    {
      Width = width;
      Height = height;

      // Create the underlying xword pattern.
      for (float h = 0; h < height*Settings.Default.SquareSize.Height; h += Settings.Default.SquareSize.Height)
        for (float w = 0; w < width*Settings.Default.SquareSize.Width; w += Settings.Default.SquareSize.Width)
          m_squares.Add(new Square(w, h, Settings.Default.SquareSize.Width, Settings.Default.SquareSize.Height));
    }

    internal Canvas Canvas
    {
      get { return m_canvas; }
      set
      {
        m_canvas = value;
        m_canvas.Paint += Paint;
        m_canvas.MouseUp += MouseUp;
        m_canvas.MouseDown += MouseDown;
        m_canvas.MouseMove += MouseMove;
        m_canvas.MouseDoubleClick += MouseDoubleClick;
        m_canvas.CanvasBounds = new RectangleF(0, 0, Width*Settings.Default.SquareSize.Width,
                                               Height*Settings.Default.SquareSize.Height);
      }
    }

    public int Width { get; set; }
    public int Height { get; set; }

    public bool CanDelete
    {
      get { return m_squares.Selected.Count != 0; }
    }

    private void MouseDoubleClick(object sender, MouseEventArgs mouseEventArgs)
    {
      Square hitSquare = m_squares.Hit(PageCoordinates(mouseEventArgs));
      if (hitSquare == null) return;
      if (EditQuestion(hitSquare)) return;
      if (EditPicture(hitSquare)) return;
      Word word = Word(hitSquare);
      if (word == null) return;
      word.Invalidate(Canvas);
      StartMoving(word);
      word.Bend(hitSquare);
      StopMoving(word);
      word.Invalidate(Canvas);
    }

    private bool EditQuestion(Square hitSquare)
    {
      var question = hitSquare as Question;
      if (question != null && Control.ModifierKeys == Keys.Control)
      {
        question.Edit(Canvas);
        return true;
      }
      return false;
    }

    private bool EditPicture(Square hitSquare)
    {
      var picture = hitSquare as Picture;
      if (picture != null) 
      {
        if (picture.Edit(Canvas))
        {
          if (picture.Sentense != null)
            foreach (Word word in picture.Sentense)
            {
              word.Select = true;
              Delete();
            }
          picture.Sentense = new Sentense(picture);
          foreach (string word in Question.Split(picture.Value.Trim()))
          {
            if (String.IsNullOrWhiteSpace(word)) continue;
            Square[] squares = new Square[word.Length];
            for (int sq = 0; sq < word.Length; ++sq)
            {
              squares[sq] = new Square(sq*Settings.Default.SquareSize.Width,
                0, Settings.Default.SquareSize.Width,
                Settings.Default.SquareSize.Height);
            }
            Word w = new Word(squares);
            for (int sq = 0; sq < word.Length; ++sq)
            {
              Letter letter = w[sq] as Letter;
              if (letter != null) letter.Character = Char.ToUpper(word[sq]);
            }
            Add(w);
            StopMoving(w);
            picture.Sentense.AddWord(w);
            m_squares.AddRange(w);
          }
        }
        return true;
      }
      return false;
    }

    /// <summary>
    /// Add a new word if the selected square is more than 1.
    /// </summary>
    public void AddWord()
    {
      Squares squares = m_squares.Selected;
      if (squares.Count <= 1) return;
      // Create new word
      var word = new QuestionWord(squares);
      // Remove old selected squares
      foreach (Square square in squares.Where(square => !(square is ResizableSquare)))
        m_squares.Remove(square);
      // Add new word squares
      m_squares.AddRange(word);
      // Add the word
      Add(word);
      // Invalidate word
      word.Invalidate(Canvas);
    }

    private Word Word(Square square)
    {
      return (from word in this from s in word where s.Contains(square) select word).FirstOrDefault();
    }

    /// <summary>
    /// Paint the xword.
    /// </summary>
    protected internal void Paint(object sender, PaintEventArgs paintEventArgs)
    {
      Paint(paintEventArgs.Graphics);
    }

    public void Paint(Graphics graphics)
    {
      Canvas.Draw(graphics);
      m_squares.Paint(graphics);
    }

    private void Unselect()
    {
      m_squares.Unselect(Canvas);
    }

    private void InvalidateSelected()
    {
      Squares seletedSquares = m_squares.Selected;
      foreach (Square square in seletedSquares)
        m_squares.Remove(square);
      m_squares.AddRange(seletedSquares);
      seletedSquares.InvalidateSelected(Canvas);
    }

    private void Unhover()
    {
      m_squares.Unhover(Canvas);
    }

    private MouseEventArgs PageCoordinates(MouseEventArgs mouseEventArgs)
    {
      Point location =
        Point.Round(Canvas.PageCoordinates(new PointF(mouseEventArgs.Location.X, mouseEventArgs.Location.Y)));
      return new MouseEventArgs(mouseEventArgs.Button, mouseEventArgs.Clicks, location.X, location.Y,
                                mouseEventArgs.Delta);
    }

    public void Delete()
    {
      StartMoving(m_squares.Selected);
      foreach (Square square in new Squares(m_squares.Selected))
      {
        square.Invalidate(Canvas);
        m_squares.Remove(square);
        Word word = Word(square);
        if (word != null) Remove(word);
      }
    }

    public void KeyPressed(KeyPressEventArgs keyPressEventArgs)
    {
      Squares selected = m_squares.Selected;
      if (selected == null) return;
      Word word = Word(selected.First());
      if (word == null) return;
      Square square = word.Cursor;
      if (square == null) return;
      var letter = square as Letter;
      if (letter == null) return;
      letter.Character = Char.ToUpperInvariant(keyPressEventArgs.KeyChar);
      letter.Invalidate(Canvas);
    }

    #region Mouse Move

    public void MouseMove(object sender, MouseEventArgs mouseEventArgs)
    {
      switch (mouseEventArgs.Button)
      {
        case MouseButtons.None:
          MouseHover(PageCoordinates(mouseEventArgs));
          break;
        case MouseButtons.Left:
          MouseMoveLeft(PageCoordinates(mouseEventArgs));
          break;
        case MouseButtons.Right:
          MouseMoveRight(PageCoordinates(mouseEventArgs));
          break;
      }
    }

    private void MouseMoveRight(MouseEventArgs mouseEventArgs)
    {
      throw new NotImplementedException();
    }

    private void MouseMoveLeft(MouseEventArgs mouseEventArgs)
    {
      Squares selected = m_squares.Selected;
      if (selected.Count == 0) return;
      foreach (Square hitSquare in from s in selected where !(s is ResizableSquare) select m_squares.Hit(mouseEventArgs))
      {
        if (hitSquare == null || hitSquare.Selected) return;
        hitSquare.Selected = true;
        hitSquare.Invalidate(Canvas);
        return;
      }

      Cursor anyHandleHit = Cursors.Default;
      foreach (ResizableSquare resizable in
        m_squares.Selected.OfType<ResizableSquare>().Where(resizable => resizable.HandleCollection.Moving != null))
      {
        resizable.Invalidate(Canvas);
        Canvas.Cursor = resizable.HandleHit(mouseEventArgs);
        if (Canvas.Cursor != Cursors.Default && anyHandleHit == Cursors.Default)
          anyHandleHit = Canvas.Cursor;
        resizable.Invalidate(Canvas);
      }
      if (anyHandleHit != Cursors.Default)
        return;

      // Check if this is the first move event.
      if (!selected[0].Moving)
        StartMoving(selected);
      selected.Invalidate(Canvas);
      Canvas.Cursor = selected.Move(Canvas, mouseEventArgs, m_mouseDownPosition);
      selected.Invalidate(Canvas);
      m_mouseDownPosition = mouseEventArgs;
    }

    private void StartMoving(Squares selected)
    {
      // Special case for picture's don't fill in with an empty square
      if (selected.Count() == 1 && selected[0] is Picture) return;
      foreach (Square square in selected.Where(square => m_squares.SquaresAt(square).Count == 1))
      {
        float x = (float)(Settings.Default.SquareSize.Width * Math.Truncate(square.Rectangle.Left / Settings.Default.SquareSize.Width));
        float y = (float)( Settings.Default.SquareSize.Height * Math.Truncate(square.Rectangle.Top / Settings.Default.SquareSize.Height));
        m_squares.Add(new Square(x, y, Settings.Default.SquareSize.Width, Settings.Default.SquareSize.Height));
      }
    }

    private void StopMoving(Squares selected)
    {
      PointF snap = selected.Snap();
      foreach (ResizableSquare square in selected.OfType<ResizableSquare>())
      {
        square.Snap(snap);
        square.Invalidate(Canvas);
        Drop(square);
      }
    }

    private void MouseHover(MouseEventArgs mouseEventArgs)
    {
      Square hitSquare = m_squares.Hit(mouseEventArgs);
      if (hitSquare == null) return;
      ResizableSquare resizableSquare = hitSquare as ResizableSquare;
      if (hitSquare.Selected && resizableSquare != null)
      {
        Canvas.Cursor = resizableSquare.HandleHit(mouseEventArgs);
      }
      else
        Canvas.Cursor = Cursors.Default;

      if (hitSquare.Hover) return;
      hitSquare.Hover = true; // Make sure it's invalidated
      Unhover();
      hitSquare.Hover = true; // Make sure it's drawn hovering
    }

    #endregion

    #region Mouse Down

    private MouseEventArgs m_mouseDownPosition;

    public void MouseDown(object sender, MouseEventArgs mouseEventArgs)
    {
      m_mouseDownPosition = PageCoordinates(mouseEventArgs);
      switch (mouseEventArgs.Button)
      {
        case MouseButtons.Left:
          MouseDownLeft(m_mouseDownPosition);
          break;
        case MouseButtons.Right:
          MouseDownRight(m_mouseDownPosition);
          break;
      }
    }

    private void MouseDownRight(MouseEventArgs mouseEventArgs)
    {
      throw new NotImplementedException();
    }

    private void MouseDownLeft(MouseEventArgs mouseEventArgs)
    {
      Square hitSquare = m_squares.Hit(mouseEventArgs);
      if (hitSquare == null)
      {
        Trace.WriteLine("XWord contains " + m_squares.Count + " squares");
        Trace.WriteLine("XWord has " + m_squares.Selected.Count + " selected squares");
        Trace.WriteLine("XWord has " + Count + " words");
        Unselect();
        return;
      }
      hitSquare.Selected = true; // Make sure it's invalidated
      if (Control.ModifierKeys == Keys.Control)
        InvalidateSelected();
      else
      {
        Unselect();
        hitSquare.Selected = true; // Make sure it's drawn selected
      }

      Word word = Word(hitSquare);
      if (word == null) return;
      if (Control.ModifierKeys != Keys.Control)
        Unselect();
      word.Select = true;
      InvalidateSelected();
      foreach (ResizableSquare resizableSquare in word.OfType<ResizableSquare>())
        resizableSquare.HandleHit(mouseEventArgs);
    }

    #endregion

    #region Mouse Up

    public void MouseUp(object sender, MouseEventArgs mouseEventArgs)
    {
      switch (mouseEventArgs.Button)
      {
        case MouseButtons.None:
          break;
        case MouseButtons.Left:
          MouseUpLeft();
          break;
        case MouseButtons.Right:
          break;
      }
    }

    private void MouseUpLeft()
    {
      // Snap to grid!
      m_squares.InvalidateSelected(Canvas);
      StopMoving(m_squares.Selected);
    }

    /// <summary>
    /// After a move a square is dropped somewhere. Any square "below" must
    /// be replaced if it is a square. Any other "higher" order of squares
    /// must be retained and errors indicated if they are incompatible.
    /// </summary>
    /// <param name="square"></param>
    internal void Drop(Square square)
    {
      foreach (Square sq in m_squares.SquaresAt(square).Where(sq => !(sq is ResizableSquare)))
        m_squares.Remove(sq);
      square.Error = false;
      foreach (Square sq in m_squares.SquaresAt(square).Where(sq => !sq.Equals(square)).Where(sq => !sq.DropOk(square)))
      {
        sq.Error = true;
        square.Error = true;
      }
    }

    #endregion

    public void AddPicture()
    {
      Picture picture = new Picture(0f, 0f, 4 * Properties.Settings.Default.SquareSize.Width, 4 * Settings.Default.SquareSize.Height);
      m_squares.Insert(0,picture);
      picture.Invalidate(Canvas);
    }

    internal void Print(System.Drawing.Printing.PrintPageEventArgs e)
    {
      e.Graphics.TranslateTransform(e.MarginBounds.Left + (e.MarginBounds.Width - Width * Settings.Default.SquareSize.Width) / 2, 
        e.MarginBounds.Top + (e.MarginBounds.Height - Height * Settings.Default.SquareSize.Height) / 2);
      Unselect();
      Unhover();
      Paint(e.Graphics);
    }
  }
}